- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.8k
          WIP: New lint: needless_path_new
          #14895
        
          New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
| I think you need to check that you can use a  Don't forget to look at the lintcheck logs for your PR. You'll see the first hit is probably wrong, as it requires building a  | 
| 
 oh, didn't know about that! thank you, that's very helpful 
 I thought I already covered that with the following line: && implements_asref_path(*parameter)I even have a test case for exactly this! // no warning
fn takes_path(_: &Path) {}
takes_path(Path::new("foo"));But now that I think about it, the check above doesn't actually work?... It just checks that whatever type  I guess what I need to check instead is that  | 
| But the weirdest thing is that  EDIT: indeed it does. but I guess this is not actually the root cause of the problem, so we could keep it? Maybe the function/method restriction should not even be necessary, maybe we should just check that the place the expression is in requires an  let _: Option<impl AsRef<Path>>: Some(Path::new("foo.txt"));(assuming  | 
| let implements_asref_path = |arg| implements_trait(cx, arg, asref_def_id, &[path_ty.into()]); | ||
|  | ||
| if let ty::FnDef(..) | ty::FnPtr(..) = type_definition.kind() { | ||
| let parameters = type_definition.fn_sig(tcx).skip_binder().inputs(); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this whole thing a bit more.
As I said towards the end of this comment, what we want to check is whether a particular parameter of a function is impl AsRef<Path>, i.e. find a p such that fn foo(p: impl AsRef<Path>).
But the latter is just syntax sugar for fn foo<P: AsRef<Path>>(p: P), so we are in fact looking for a parameter whose type is defined in a generic type parameter of the function.
Therefore I think we shouldn't actually calling skip_binder here. Without that, I get the following chain:
- The type of type_definition.fn_sig(tcx)isPolyFnSig, which is an alias toBinder<'tcx, FnSig<'tcx>>
- that one has a Binder::inputsmethod, which returns aBinder<'tcx, &'tcx [Ty<'tcx>]>
- I can kind of iterate over that using Binder::iter, which finally gives meimpl Iterator<Item=Binder<'tcx, Ty<'tcx>>>
- and now I'm stuck. What I think I have achieved at this point is turn a function like fn foo<P, T>(p: P, t: T, n: u32)into something like[ p<P,T>: P, t<P, T>: T, n<P, T>: u32 ](syntax very much pseudocode-ish). So from there I need to first "shed" the unnecessary generics, and then?... No idea to be honest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, this slipped through, indeed Zulip would be a better choice for those kind of discussion. Have you read the documentation on ParamEnv? You could get the caller bounds that the caller has to adhere to for the parameter index you're interested in, and check whether the type of the argument would meet those bounds (so that for example a bound of impl AsRef<Path> + Clone would not receive an object which implements AsRef<Path> but is not Cloneable).
Also, you will have to check that the type of the parameter you're interested in does not appear in another parameter or a clause applying to another parameter type, as for example
fn foo<P: AsRef<Path>>(x: P, y: P) { … }must use the same type for both parameters, and you better let this untouched.
Does that help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, the whole Analysis section from the dev-guide seems to be exactly what I need! Thank you very much for this and all the other pointers, I'll read them and see what I come up with –and ask on Zulip if I don't understand something
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
| @samueltardieu just pinging in case the notifications slipped through. If you can't/don't want to deal with this, I'll probably ask the Zulip folks for help again? | 
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
| Currently making the feature freeze feature and the performance project perform, I don't have the capacity right now to review this :/ r? clippy | 
| Oh hi! All good, I'm still very much not done with this 😅 We're slowly making progress over on Zulip: https://rust-lang.zulipchat.com/#narrow/channel/257328-clippy/topic/needless_path_new/with/525402801 | 
| 
 No problem, when you consider that you're in a ready-to-review state, you can mention the reviewer assigned to take a look @rustbot author | 
| Reminder, once the PR becomes ready for a review, use  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r? Jarcho
I can take over the review for this.
| let generic_args_we_can_change: Vec<_> = generic_args | ||
| .iter() | ||
| .filter_map(|g| g.as_type()) | ||
| // if a generic is used in multiple places, we should better not touch it, | ||
| // since we'd need to suggest changing both parameters that using it at once, | ||
| // which might not be possible | ||
| .filter(|g| { | ||
| let inputs_and_output = fn_sig.inputs().iter().copied().chain([fn_sig.output()]); | ||
| inputs_and_output.filter(|i| i.contains(*g)).count() < 2 | ||
| }) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is better determined once we find a call to Path::new lines up with a generic argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this was just a WIP thing to see if it'll even work (and have something to link to for the folks on Zulip)
99a3e7b    to
    599b3d7      
    Compare
  
    | Lintcheck changes for 945d5e4 
 This comment will be updated if you push new changes | 
84862cf    to
    d014c46      
    Compare
  
    d014c46    to
    a5dd45b      
    Compare
  
    
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
a5dd45b    to
    a7fb9ec      
    Compare
  
    
      
        
              This comment has been minimized.
        
        
      
    
  This comment has been minimized.
a7fb9ec    to
    2feab73      
    Compare
  
    2feab73    to
    148fda1      
    Compare
  
    | This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. | 
148fda1    to
    c7a07b9      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've certainly learned a lot in these.. 4 months, wow.
This pretty much works now, here are some things I'm personally not fully sure about.
@rustbot ready
| .filter_map(|(clause, _)| { | ||
| let clause = clause.kind(); | ||
| #[expect(clippy::match_same_arms, reason = "branches have different reasons to be `None`")] | ||
| match clause.skip_binder() { | ||
| // This is what we analyze | ||
| // NOTE: repeats the contents of `Clause::as_trait_clause`, | ||
| // except we don't `Binder::rebind` as we don't care about the binder | ||
| ClauseKind::Trait(trait_clause) => Some(trait_clause), | ||
|  | ||
| // Trivially holds for `P`: `Path::new` has signature `&S -> &Path`, so any "outlives" that holds for | ||
| // `P` does so for `S` as well | ||
| ClauseKind::TypeOutlives(_) | ClauseKind::RegionOutlives(_) => None, | ||
|  | ||
| // Irrelevant to us: neither `AsRef` nor `Sized` have associated types | ||
| ClauseKind::Projection(_) => None, | ||
|  | ||
| // Irrelevant: we don't have anything to do with consts | ||
| ClauseKind::ConstArgHasType(..) | ClauseKind::ConstEvaluatable(_) | ClauseKind::HostEffect(_) => None, | ||
|  | ||
| // Irrelevant?: we don't deal with unstable impls | ||
| ClauseKind::UnstableFeature(_) => None, | ||
|  | ||
| ClauseKind::WellFormed(_) => None, | ||
| } | ||
| }) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See last commit -- should I keep this fully explicit match, or should I just revert to `clause.as_trait_clause()? The main reason I like this is that I can enumerate why I dismiss all the clause kinds other than the trait predicates -- but maybe there is a more concise way of doing that
| // match cx.tcx.get_diagnostic_name(pred.def_id()) { | ||
| // Some(sym::AsRef) => { | ||
| // // TODO: check if it's `AsRef<Path>` in paricular | ||
| // }, | ||
| // Some(sym::Sized) => todo!(), | ||
| // _ => return false, | ||
| // }; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to forego checking for exactly AsRef and Sized, because a) imo that makes the lint more correct, and b) is just easier to explain in the lint description lol.
If you agree, I'd remove this WIP code, otherwise I'll continue on from it
c7a07b9    to
    b5660db      
    Compare
  
    b5660db    to
    945d5e4      
    Compare
  
    | ☔ The latest upstream changes (possibly 5b23bd4) made this pull request unmergeable. Please resolve the merge conflicts. | 
fs::read_to_string(Path::new("foo.txt"))->fs::read_to_string("foo.txt")Resolves #14668
changelog: [
needless_path_new]: new lint